﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace TtfLibrary
{
    public class TtfStructureReader
    {
        public byte[] Content;
        public int Position = 0;

        public Stack<int> RegionStack = new Stack<int>();

        public void StartRegion()
        {
            RegionStack.Push(Position);
        }

        public byte[] EndRegionAndGetContent()
        {
            var startPos = RegionStack.Pop();
            var endPos = Position;
            var length = endPos - startPos;

            return Content.Skip(startPos).Take(length).ToArray();
        }

        public void Read(object obj, string[] parts = null)
        {
            var objectStartOffset = Position;

            var objType = obj.GetType();
            var fields = objType.GetFields();

            string currentPartName = null;
            foreach (var field in fields)
            {
                if (field.IsStatic)
                    continue;

                var fieldType = field.FieldType;

                if (field.GetCustomAttributes(typeof(TtfStopAttribute), false).Length != 0)
                    break;

                var part = field.GetCustomAttributes(typeof(TtfPartAttribute), false).FirstOrDefault() as TtfPartAttribute;
                if (part != null)
                    currentPartName = part.Name;
                if (parts != null && !parts.Contains(currentPartName) || parts == null && currentPartName != null)
                    continue;

                var value = ReadScalarType(fieldType);
                if (value != null)
                    field.SetValue(obj, value);
                else if (fieldType.GetCustomAttributes(typeof(TtfStructureAttribute), false).Length != 0)
                {
                    value = field.GetValue(obj);

                    if (value == null)
                    {
                        value = Read(fieldType);
                        field.SetValue(obj, value);
                    }
                    else
                        Read(value);
                }
                else if (fieldType.IsArray)
                {
                    var array = (Array)field.GetValue(obj);
                    var itemType = fieldType.GetElementType();

                    if (array != null && array.Rank == 1)
                        for (int i = 0; i < array.Length; i++)
                            array.SetValue(Read(itemType), i);
                }
                else
                {
                    Console.WriteLine(String.Format("Unknown field type: {0}", field.FieldType.Name));
                    break;
                }
            }

            if (parts == null && obj is IAfterRead)
                ((IAfterRead)obj).AfterRead(this, objectStartOffset);
        }

        public object Read(Type type)
        {
            var obj = ReadScalarType(type);
            if (obj == null)
            {
                obj = Activator.CreateInstance(type);
                Read(obj);
            }
            return obj;
        }

        public T Read<T>()
        {
            return (T)Read(typeof(T));
        }

        public object ReadScalarType(Type type)
        {
            if (type == typeof(UInt16)) return (UInt16)ReadValue(2);
            else if (type == typeof(UInt32)) return (UInt32)ReadValue(4);
            else if (type == typeof(UInt64)) return (UInt64)ReadValue(8);
            else if (type == typeof(Int16)) return (Int16)ReadValue(2);
            else if (type == typeof(Int32)) return (Int32)ReadValue(4);
            else if (type == typeof(Int64)) return (Int64)ReadValue(8);
            else if (type == typeof(byte)) return (byte)ReadValue(1);
            else if (type == typeof(char)) return (char)ReadValue(1);
            else if (type == typeof(string)) return ReadPascalString();
            else
                return null;
        }

        public T ReadScalarType<T>()
        {
            return (T)ReadScalarType(typeof(T));
        }

        public UInt64 ReadValue(int length)
        {
            UInt64 result = 0;
            for (int i = 0; i < length; i++)
                result |= (UInt64)Content[Position + i] << (length - 1 - i) * 8;
            Position += length;
            return result;
        }

        public byte[] ReadBytes(int length)
        {
            var result = Content.Skip(Position).Take(length).ToArray();
            Position += length;
            return result;
        }

        public string ReadString()
        {
            var startPos = Position;
            for (; Position < Content.Length; Position++)
                if (Content[Position] == 0)
                    break;

            var result = Encoding.Default.GetString(Content, startPos, Position - startPos);
            Position++;
            return result;
        }

        public string ReadString(int length)
        {
            var result = Encoding.Default.GetString(Content, Position, length);
            Position += length;
            return result;
        }

        public string ReadPascalString()
        {
            throw new NotImplementedException();
        }
    }
}
